package com.xiaoxin.experience.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.digests.SM3Digest;

import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @author xiaoxin
 */
@Slf4j
public class DigestUtil
{

    public static final int NONCE_LENGTH = 34;

    private static final String DIGEST_STARTER = "Digest ";

    private static final String DIGEST_METHOD = "POST";

    public static final int UNAUTHORIZED = 401;

    public static final String DEFAULT_ALGORITHM = "MD5";

    public static final String SM3_ALGORITHM = "SM3";

    private DigestUtil() {}

    /**
     * @param authenticate 首次请求响应的鉴权信息,例如:Digest realm=realm@host.com,qop=auth,nonce=d2gJ6UTWPXpxCsNINMAByW1S7v2SPIuEYg,opaque=null
     * @param username 约定的鉴权名
     * @param password 约定的密码
     * @param requestMethod 请求方式
     * @return 摘要鉴权的鉴权头部信息
     */
    public static String getAuthorization(String authenticate, String username, String password, String requestMethod,String url,byte[] body)
    {
        String uri = getUri(url);
        if (authenticate.startsWith(DIGEST_STARTER))
        {
            authenticate = authenticate.substring(DIGEST_STARTER.length());
        }
        String[] digestMessageArray = authenticate.split(",");
        Map<String,String> digestMessageMap = new HashMap<>();
        for (String digestMessage : digestMessageArray)
        {
            int sepIndex = digestMessage.indexOf("=");
            if (sepIndex > 0)
            {
                String value = digestMessage.substring(sepIndex + 1);
                value = value.replaceAll("\"", "");
                digestMessageMap.put(digestMessage.substring(0, sepIndex).trim(), value.trim());
            }
        }
        String realm = digestMessageMap.get("realm");

        String nonce = digestMessageMap.get("nonce");
        String algorithm = Optional.ofNullable(digestMessageMap.get("algorithm")).orElse("MD5");
        String qop = digestMessageMap.get("qop");
        String cnonce = digestMessageMap.get("cnonce");
        String nc = digestMessageMap.get("nc");
        if (StringUtils.isBlank(qop) && StringUtils.isBlank(cnonce) && StringUtils.isBlank(nc))
        {
            String response = generateResponse(username, realm, nonce, uri, password, requestMethod, algorithm);
            String authHead = "Digest username=\"%s\",realm=\"%s\",algorithm=%s,uri=\"%s\",nonce=\"%s\",response=\"%s\"";
            return String.format(authHead, username, realm, algorithm, uri, nonce, response);

        }
        qop = StringUtils.isBlank(qop) ? "auth" : qop;
        cnonce = StringUtils.isBlank(cnonce) ? RandomUtil.randomString(32) : cnonce;
        nc = StringUtils.isBlank(nc) ? "00000001" : nc;

        String digestResponse = generateResponse(username, realm, password, algorithm, qop, uri, requestMethod, body, nonce, nc, cnonce);
        String authHead =
                "Digest username=\"%s\",realm=\"%s\",qop=\"%s\",algorithm=%s,uri=\"%s\",nonce=\"%s\",nc=%s,cnonce=\"%s\",response=\"%s\"";
        return String.format(authHead, username, realm, qop,algorithm,uri,nonce,nc, cnonce, digestResponse);
    }


    public static String generateResponse(String username,String realm,String password,String algorithm,String qop,String uri,String requestMethod,byte[] body,String nonce,String nc,String cnonce)
    {
        //构造userName,realm,password
        String a1 = digest(generateA1(username, realm, password), algorithm);

        //第一次响应
        String a2 = digest(firstA2(qop, uri, requestMethod, body), algorithm);

        String genString = StringUtil.appendStr(a1, ":", nonce, ":", nc, ":", cnonce, ":", qop, ":", a2);
        return digest(genString, algorithm);
    }

    /**
     * 加密生成鉴权中的response值
     *
     * @param userName  约定的用户名
     * @param realm     约定的域
     * @param nonce     服务器用来鉴定客户端的摘要质询参数
     * @param url       访问路径
     * @param password  约定的密码
     * @param method    请求METHOD
     * @param algorithm 摘要算法
     * @return 加密后的值
     */
    public static String generateResponse(String userName, String realm, String nonce, String url,
                                          String password, String method, String algorithm)
    {
        //构造userName,realm,password
        String a1 = digest(generateA1(userName, realm, password), algorithm);

        //第一次响应
        String a2 = digest(firstA2(url, method), algorithm);

        String genString = StringUtil.appendStr(a1, ":", nonce, ":", a2);

        return digest(genString, algorithm);
    }

    private static String firstA2(String url, String method)
    {
        if (Objects.isNull(method) || method.length() < 1)
        {
            method = DIGEST_METHOD;
        }
        return method + ":" + url;
    }

    private static String getUri(String url)
    {
        if (url.startsWith("http"))
        {
            try
            {
                return new URL(url).getPath();
            }
            catch (Exception ignore)
            {}
        }
        return url;
    }

    public static String getAuthenticate(String uri)
    {
        try
        {
            URL url = new URL(uri);
            URLConnection urlConnection = url.openConnection();
            HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;
            int responseCode = httpURLConnection.getResponseCode();
            if (UNAUTHORIZED == responseCode)
            {
                Map<String, List<String>> headerFields = httpURLConnection.getHeaderFields();
                return headerFields.get("WWW-Authenticate").get(0);
            }
        }
        catch (Exception ignored)
        { }
        return null;
    }

    /**
     * 使用指定哈希算法计算摘要信息
     *
     * @param content   内容
     * @param algorithm 哈希算法
     * @return 内容摘要
     */
    public static String digest(String content, String algorithm)
    {
        try
        {
            if (StringUtils.isBlank(algorithm))
            {
                algorithm = DEFAULT_ALGORITHM;
            }
            byte[] data = content.getBytes(StandardCharsets.UTF_8);
            if (SM3_ALGORITHM.equals(algorithm))
            {
                SM3Digest digest = new SM3Digest();
                digest.update(data, 0, data.length);
                byte[] hash = new byte[digest.getDigestSize()];
                digest.doFinal(hash, 0);
                return bytesToHexString(hash);
            }
            return bytesToHexString(MessageDigest.getInstance(algorithm).digest(data));
        }
        catch (NoSuchAlgorithmException e)
        {
            return null;
        }
    }

    /**
     * 将字节数组转换成16进制字符串
     *
     * @param bytes 即将转换的数据
     * @return 16进制字符串
     */
    private static String bytesToHexString(byte[] bytes)
    {
        StringBuilder builder = new StringBuilder(bytes.length);
        String temp;
        for (byte aByte : bytes)
        {
            temp = Integer.toHexString(0xFF & aByte);
            if (temp.length() < 2)
            {
                builder.append(0);
            }
            builder.append(temp);
        }
        return builder.toString();
    }

    private static String generateA1(String userName, String realm, String password)
    {
        return StringUtil.appendStr(userName, ":", realm, ":", password);
    }

    private static String firstA2(String qop, String url, String method, byte[] body)
    {
        if (StringUtils.isBlank(method))
        {
            method = DIGEST_METHOD;
        }

        if ("auth".equals(qop))
        {
            return method + ":" + url;
        }
        else if ("auth-int".equals(qop))
        {
            String entityBody = new String(body, StandardCharsets.UTF_8);
            return StringUtil.appendStr(method, ":", url, ":", MD5.md5(entityBody));
        }
        return "";
    }
}
